﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Security;
using System.Runtime.InteropServices;
using System.Diagnostics;
using System.Security.Principal;
using System.ComponentModel;
using System.IO;

namespace SH_VBoxVM_Service.Classes
{
    [SuppressUnmanagedCodeSecurity]
    class ApplicationLoader
    {
        /// <summary>
        /// No Need to create the class.
        /// </summary>
        private ApplicationLoader() { }

        private static List<Process> listSuspendedProcesses = new List<Process>();
  

        enum TOKEN_INFORMATION_CLASS
        {
            //The buffer receives a TOKEN_USER structure that contains the user account of the token.
            TokenUser = 1,

            // The buffer receives a TOKEN_GROUPS structure that contains the group accounts associated with the token.
            TokenGroups,

            // The buffer receives a TOKEN_PRIVILEGES structure that contains the privileges of the token.
            TokenPrivileges,

            // The buffer receives a TOKEN_OWNER structure that contains the default owner security identifier (SID) for newly created objects.
            TokenOwner,

            // The buffer receives a TOKEN_PRIMARY_GROUP structure that contains the default primary group SID for newly created objects.
            TokenPrimaryGroup,

            // The buffer receives a TOKEN_DEFAULT_DACL structure that contains the default DACL for newly created objects.
            TokenDefaultDacl,

            // The buffer receives a TOKEN_SOURCE structure that contains the source of the token. TOKEN_QUERY_SOURCE access is needed to retrieve this information.
            TokenSource,

            // The buffer receives a TOKEN_TYPE value that indicates whether the token is a primary or impersonation token.
            TokenType,

            // The buffer receives a SECURITY_IMPERSONATION_LEVEL value that indicates the impersonation level of the token. If the access token is not an impersonation token, the function fails.
            TokenImpersonationLevel,

            // The buffer receives a TOKEN_STATISTICS structure that contains various token statistics.
            TokenStatistics,

            // The buffer receives a TOKEN_GROUPS structure that contains the list of restricting SIDs in a restricted token.
            TokenRestrictedSids,

            // The buffer receives a DWORD value that indicates the Terminal Services session identifier that is associated with the token. 
            TokenSessionId,

            // The buffer receives a TOKEN_GROUPS_AND_PRIVILEGES structure that contains the user SID, the group accounts, the restricted SIDs, and the authentication ID associated with the token.
            TokenGroupsAndPrivileges,

            // Reserved.
            TokenSessionReference,

            // The buffer receives a DWORD value that is nonzero if the token includes the SANDBOX_INERT flag.
            TokenSandBoxInert,

            // Reserved.
            TokenAuditPolicy,

            // The buffer receives a TOKEN_ORIGIN value. 
            TokenOrigin,

            // The buffer receives a TOKEN_ELEVATION_TYPE value that specifies the elevation level of the token.
            TokenElevationType,

            // The buffer receives a TOKEN_LINKED_TOKEN structure that contains a handle to another token that is linked to this token.
            TokenLinkedToken,

            // The buffer receives a TOKEN_ELEVATION structure that specifies whether the token is elevated.
            TokenElevation,

            // The buffer receives a DWORD value that is nonzero if the token has ever been filtered.
            TokenHasRestrictions,

            // The buffer receives a TOKEN_ACCESS_INFORMATION structure that specifies security information contained in the token.
            TokenAccessInformation,

            // The buffer receives a DWORD value that is nonzero if virtualization is allowed for the token.
            TokenVirtualizationAllowed,

            // The buffer receives a DWORD value that is nonzero if virtualization is enabled for the token.
            TokenVirtualizationEnabled,

            // The buffer receives a TOKEN_MANDATORY_LABEL structure that specifies the token's integrity level. 
            TokenIntegrityLevel,

            // The buffer receives a DWORD value that is nonzero if the token has the UIAccess flag set.
            TokenUIAccess,

            // The buffer receives a TOKEN_MANDATORY_POLICY structure that specifies the token's mandatory integrity policy.
            TokenMandatoryPolicy,

            // The buffer receives the token's logon security identifier (SID).
            TokenLogonSid,

            // The maximum value for this enumeration
            MaxTokenInfoClass
        }


        [StructLayout(LayoutKind.Sequential)]
        public struct STARTUPINFO
        {
            public Int32 cb;
            public string lpReserved;
            public string lpDesktop;
            public string lpTitle;
            public Int32 dwX;
            public Int32 dwY;
            public Int32 dwXSize;
            public Int32 dwXCountChars;
            public Int32 dwYCountChars;
            public Int32 dwFillAttribute;
            public Int32 dwFlags;
            public Int16 wShowWindow;
            public Int16 cbReserved2;
            public IntPtr lpReserved2;
            public IntPtr hStdInput;
            public IntPtr hStdOutput;
            public IntPtr hStdError;
        }

        [StructLayout(LayoutKind.Sequential)]
        public struct PROCESS_INFORMATION
        {
            public IntPtr hProcess;
            public IntPtr hThread;
            public Int32 dwProcessID;
            public Int32 dwThreadID;
        }

        [StructLayout(LayoutKind.Sequential)]
        public struct SECURITY_ATTRIBUTES
        {
            public Int32 Length;
            public IntPtr lpSecurityDescriptor;
            public bool bInheritHandle;
        }

        public enum SECURITY_IMPERSONATION_LEVEL
        {
            SecurityAnonymous,
            SecurityIdentification,
            SecurityImpersonation,
            SecurityDelegation
        }

        public enum TOKEN_TYPE
        {
            TokenPrimary = 1,
            TokenImpersonation
        }

        /****  Process Creation Flags ****/
        //Just creates the process with Default Settings.
        public const int GENERIC_ALL_ACCESS = 0x10000000;
        //Creates the Process without a GUI Windows or Console Window
        public const int CREATE_NO_WINDOW = 0x08000000;
        //Create process in a thread as a Suspended State, call Resume to be able to continue the process.
        public const int CREATE_SUSPEDED = 0x00000004;

        [DllImport("advapi32.dll", EntryPoint = "ImpersonateLoggedOnUser", SetLastError = true,
              CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)]
        public static extern IntPtr ImpersonateLoggedOnUser(IntPtr hToken);

        [
           DllImport("kernel32.dll",
              EntryPoint = "CloseHandle", SetLastError = true,
              CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)
        ]
        public static extern bool CloseHandle(IntPtr handle);

        [
           DllImport("advapi32.dll",
              EntryPoint = "CreateProcessAsUser", SetLastError = true,
              CharSet = CharSet.Ansi, CallingConvention = CallingConvention.StdCall)
        ]
        public static extern bool
           CreateProcessAsUser(IntPtr hToken, string lpApplicationName, string lpCommandLine,
                               ref SECURITY_ATTRIBUTES lpProcessAttributes, ref SECURITY_ATTRIBUTES lpThreadAttributes,
                               bool bInheritHandle, Int32 dwCreationFlags, IntPtr lpEnvrionment,
                               string lpCurrentDirectory, ref STARTUPINFO lpStartupInfo,
                               ref PROCESS_INFORMATION lpProcessInformation);

        [
           DllImport("advapi32.dll",
              EntryPoint = "DuplicateTokenEx")
        ]
        public static extern bool
           DuplicateTokenEx(IntPtr hExistingToken, Int32 dwDesiredAccess,
                            ref SECURITY_ATTRIBUTES lpThreadAttributes,
                            Int32 ImpersonationLevel, Int32 dwTokenType,
                            ref IntPtr phNewToken);


        [DllImport("Kernel32.dll", SetLastError = true)]
        //[return: MarshalAs(UnmanagedType.U4)]
        public static extern IntPtr WTSGetActiveConsoleSessionId();

        [DllImport("advapi32.dll")]
        public static extern IntPtr SetTokenInformation(IntPtr TokenHandle, IntPtr TokenInformationClass, IntPtr TokenInformation, IntPtr TokenInformationLength);


        [DllImport("wtsapi32.dll", SetLastError = true)]
        public static extern bool WTSQueryUserToken(uint sessionId, out IntPtr Token);

        private static int getCurrentUserSessionID()
        {
            uint dwSessionId = (uint)WTSGetActiveConsoleSessionId();

            //Gets the ID of the User logged in with WinLogOn
            Process[] processes = Process.GetProcessesByName("winlogon");
            foreach (Process p in processes)
            {
                if ((uint)p.SessionId == dwSessionId)
                {

                    //this is the process controlled by the same sessionID
                    return p.SessionId; 
                }
            }

            return -1;
        }
 


        /// <summary>
        /// Actually calls and creates the application.
        /// TODO: Refactor this method into smaller bits.
        /// </summary>
        /// <param name="filename"></param>
        /// <param name="args"></param>
        /// <returns></returns>
        public static Process CreateProcessAsUser(string filename, string args)
        {
            //var replaces IntPtr
            var hToken = WindowsIdentity.GetCurrent().Token; //gets Security Token of Current User.

            
            var hDupedToken = IntPtr.Zero;

            var pi = new PROCESS_INFORMATION();
            var sa = new SECURITY_ATTRIBUTES();
            sa.Length = Marshal.SizeOf(sa);

            try
            {
                //Not sure if this method of DuplicateTokensEx is any longer needed.
                if (!DuplicateTokenEx(
                        hToken,
                        GENERIC_ALL_ACCESS,
                        ref sa,
                        (int)SECURITY_IMPERSONATION_LEVEL.SecurityIdentification,
                        (int)TOKEN_TYPE.TokenPrimary,
                        ref hDupedToken
                    ))
                    throw new Win32Exception(Marshal.GetLastWin32Error());




                var si = new STARTUPINFO();
                si.cb = Marshal.SizeOf(si);
                si.lpDesktop = "";

                var path = Path.GetFullPath(filename);
                var dir = Path.GetDirectoryName(path);

                //Gets current user Session ID
                uint curSessionid = (uint)ApplicationLoader.getCurrentUserSessionID();

                if (!WTSQueryUserToken(curSessionid,out hDupedToken))
                {
                    throw new Win32Exception(Marshal.GetLastWin32Error());
                }

                // Revert to self to create the entire process; not doing this might
                // require that the currently impersonated user has "Replace a process
                // level token" rights - we only want our service account to need
                // that right.
                using (var ctx = WindowsIdentity.Impersonate(IntPtr.Zero))
                {
                    if (!CreateProcessAsUser(
                                            hDupedToken,
                                            path,
                                            string.Format("\"{0}\" {1}", filename.Replace("\"", "\"\""), args),
                                            ref sa, ref sa,
                                            false, CREATE_NO_WINDOW, IntPtr.Zero,
                                            dir, ref si, ref pi
                                    ))
                        throw new Win32Exception(Marshal.GetLastWin32Error());
                }

                return Process.GetProcessById(pi.dwProcessID);
            }
            finally
            {
                if (pi.hProcess != IntPtr.Zero)
                    CloseHandle(pi.hProcess);
                if (pi.hThread != IntPtr.Zero)
                    CloseHandle(pi.hThread);
                if (hDupedToken != IntPtr.Zero)
                    CloseHandle(hDupedToken);
            }
        }



        /// <summary>
        /// Creates a Process as Suspended and adds it to the
        /// listSuspendedProcesses List
        /// 
        /// ---
        /// @deprecated: No longer needed, idea failed because of Windows Shutdown process.
        /// </summary>
        /// <param name="filename">Actual FileName with Path</param>
        /// <param name="args"></param>
        public static void CreateProcessAsUserSuspended(string filename, string args)
        {
            //var replaces IntPtr
            var hToken = WindowsIdentity.GetCurrent().Token; //gets Security Token of Current User.


            var hDupedToken = IntPtr.Zero;

            var pi = new PROCESS_INFORMATION();
            var sa = new SECURITY_ATTRIBUTES();
            sa.Length = Marshal.SizeOf(sa);

            try
            {
                //Not sure if this method of DuplicateTokensEx is any longer needed.
                if (!DuplicateTokenEx(
                        hToken,
                        GENERIC_ALL_ACCESS,
                        ref sa,
                        (int)SECURITY_IMPERSONATION_LEVEL.SecurityIdentification,
                        (int)TOKEN_TYPE.TokenPrimary,
                        ref hDupedToken
                    ))
                    throw new Win32Exception(Marshal.GetLastWin32Error());




                var si = new STARTUPINFO();
                si.cb = Marshal.SizeOf(si);
                si.lpDesktop = "";

                var path = Path.GetFullPath(filename);
                var dir = Path.GetDirectoryName(path);

                //Testing
                uint curSessionid = (uint)ApplicationLoader.getCurrentUserSessionID();

                if (!WTSQueryUserToken(curSessionid, out hDupedToken))
                {
                    throw new Win32Exception(Marshal.GetLastWin32Error());
                }

                // Revert to self to create the entire process; not doing this might
                // require that the currently impersonated user has "Replace a process
                // level token" rights - we only want our service account to need
                // that right.
                using (var ctx = WindowsIdentity.Impersonate(IntPtr.Zero))
                {
                    if (!CreateProcessAsUser(
                                            hDupedToken,
                                            path,
                                            string.Format("\"{0}\" {1}", filename.Replace("\"", "\"\""), args),
                                            ref sa, ref sa,
                                            false, CREATE_SUSPEDED, IntPtr.Zero,
                                            dir, ref si, ref pi
                                    ))
                        throw new Win32Exception(Marshal.GetLastWin32Error());
                }
                
                
                //Adding the new Suspended Process to my List.
                Process pr = Process.GetProcessById(pi.dwProcessID);
                if (pr != null) 
                  listSuspendedProcesses.Add(pr);
            }
            finally
            {
                if (pi.hProcess != IntPtr.Zero)
                    CloseHandle(pi.hProcess);
                if (pi.hThread != IntPtr.Zero)
                    CloseHandle(pi.hThread);
                if (hDupedToken != IntPtr.Zero)
                    CloseHandle(hDupedToken);
            }
        }








        /*******************   Code Used to Resume Process Threads from WIN32API     *************************/

        [DllImport("kernel32.dll")]
        static extern int ResumeThread( IntPtr hThread );

        [DllImport("kernel32.dll")]
        static extern int SuspendThread( IntPtr hThread );

        [DllImport("kernel32.dll")]
        static extern IntPtr OpenThread( ThreadAccess dwDesiredAccess,
            bool bInheritHandle,
            uint dwThreadId
        );

        [Flags]
        public enum ThreadAccess : int
        {
            TERMINATE = ( 0x0001 ),
            SUSPEND_RESUME = ( 0x0002 ),
            GET_CONTEXT = ( 0x0008 ),
            SET_CONTEXT = ( 0x0010 ),
            SET_INFORMATION = ( 0x0020 ),
            QUERY_INFORMATION = ( 0x0040 ),
            SET_THREAD_TOKEN = ( 0x0080 ),
            IMPERSONATE = ( 0x0100 ),
            DIRECT_IMPERSONATION = ( 0x0200 )
        }



        /// <summary>
        /// Resumes all the Suspended Threads created by the Method
        /// --> CreateProcessAsUserSuspended
        /// 
        /// ---
        /// @deprecated: method no longer needed, will be deleted in future releases.
        /// </summary>
        public static void UnfreezeThreads()
        {
            //if nothing in the list, do nothing.
            if (listSuspendedProcesses.Count == 0)
                return;


            foreach(Process newpr in listSuspendedProcesses)
            {

           
                foreach ( ProcessThread pT in newpr.Threads )
                {
                    IntPtr ptrOpenThread = OpenThread(ThreadAccess.SUSPEND_RESUME, false, (uint)pT.Id);

                    if ( ptrOpenThread == null )
                        break;

                    //resumes the suspended thread.
                    ResumeThread(ptrOpenThread);
                }
            }


        }






    } //class


   } //namespace

